/*  Nanhill Simple remote object(sro) protocol JavaScript client
 *  
 * 	need Nhill util.js
 * 
 *  version 1.0.1
 *  (c) 2005 alin <xalinx@gmail.com>
 *
 *  Sro is freely distributable under the terms of an Apache license.
 *  For details, see the Nanhill web site: 
 *  http://sourceforge.net/projects/nanhillplatform
/*--------------------------------------------------------------------------*/
var Sro = Class.create();
Sro.METHOD_ID = "SRO_METHOD_ID";
Sro.SERVICE_ID = "SRO_SERVICE_ID";
Sro.prototype = {
	initialize: function(gateway, async, events) {
		this.gateway = gateway;
		this.transport = null;
		this.async = async ? async : true;
		this.currentCallback = new Function();
		this.setEvents(events);
	},
	
	getGateway : function() { return this.gateway;},

	setEvents: function(events) {
		this.events = {
			onLoading: Sro.Default.showLoading,
			onFinish: new Function(),
			onException: new Function(),
			onError: Sro.Default.showError
		};
		Object.extend(this.events, events || {});
	},

	remoteCall: function(serviceName, params, callback) {
		var loc = serviceName.split(".");	
		//Server controller is MultiControllerExporter, need service and method id.
		if(2 <= loc.length ) {
			var url = this.gateway + "?" + Sro.SERVICE_ID + "=" + loc[0] + "&" + Sro.METHOD_ID + "=" + loc[1];			
		//Server controller is SimpleControllerExporter, need method id.
		}else if (1 == loc.length) {
			var url = this.gateway+ "?" + Sro.METHOD_ID + "=" + loc[0];
		}	
		var call = new Sro.Call();
		for (var i = 0; i < params.length; i++) {
			call.addParameter(params[i]);
		}
		//alert(call.xml());
		this.transport = XmlHttp.create();
		this.transport.open("POST", url, this.async);
		this.transport.send(call.xml());
		this.currentCallback = callback;
		if (this.async) {
			this.transport.onreadystatechange = this.onStateChange.bind(this);
			this.events["onLoading"](true);
		} else { 
			this.response();
		}
	},
	
	onStateChange: function(){
		if (this.transport.readyState == 4) {
			this.response();
		}
	},
	
	response : function() {
		if (this.transport.status == '200') {
			var data = this.transport.responseText;
			this.events["onLoading"](false);
			//alert(data);
			var reply = new Sro.Reply(data);
			this.currentCallback(reply);
			this.events["onFinish"](reply);
		} else {
			this.events["onError"](this.transport.responseText);
		}
	}

}


Sro.Default = {
	loadingPane:null,
	errorPane:null,
	
	showLoading : function(state) {
		this.loadingPane = $("sro_loading");
		if (this.loadingPane == null) {
			var el = document.createElement('DIV');
			el.setAttribute("id","sro_loading");
			el.style.cssText="display:none;top:1px;font-family:Verdana;font-size:11px;border:1px solid #00CC00;background-color:#A4FFA4;padding:1px;position:absolute; right:1px; width:110px; height:14px; z-index:10000";
			el.innerHTML="data loading... ";
			document.body.appendChild(el);
			this.loadingPane = el;
		}
		if (state) {
			this.loadingPane.style.display="block";
			//this.loadingPane.style.top = document.body.scrollTop+1;
		} else {
			this.loadingPane.style.display="none";
		}
	},
	
	showError: function(errorStr) {
		this.errorPane = $("sro_error");
		if (this.errorPane == null) {
			var el = document.createElement('DIV');
			el.setAttribute("id","sro_error");
			el.style.cssText="font-family:Verdana;font-size:11px;border:1px solid #00CC00;background-color:#ffdb9c;padding:1px;position:absolute;overflow:auto; right:1px; width:500px; height:300px; z-index:1";
			el.innerHTML=errorStr;
			document.body.appendChild(el);
			this.errorPane = el;
		}
	},
	
	showException: function(ex) { /*TODO*/ }
}



Sro.Call = Class.create();
Sro.Call.prototype = {
	initialize: function(){
		this.params = [];
		this.intMin = -2147483648;
		this.intMax = 2147483647;
	},

	addParameter: function(data){
		if (arguments.length==0) return;
		this.params[this.params.length] = data;
	},

	xml: function(){
		var xmlstr = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
		xmlstr += "<sro ver=\"1.0\">\n";
		xmlstr += this.getParamXML(this.dataTypeOf(this.params),this.params);
		xmlstr += "</sro>";
		return xmlstr;   
	},
	
	/* get sro type of an javascript object */
	dataTypeOf: function (obj){
		var type = typeof(obj);
		type = type.toLowerCase();
		//switch ignore js type boolean, string, only parse number, object, function
		switch(type){
		  case "number":
			if (this.intMin < obj && this.intMax > obj) { 
				type = "int"; 
			} else {
				type = "double";
			}
			break;
		  case "object":
		  	if(null === obj) {
		  		type = "null";
		  	} else {
				var con = obj.constructor;
				if (con === Date) {
					type = "date";
				} else if (con === Array) {
					type = "list";
				} else {
					type = "map";
				}
			}
			break;
		  case "function":
		  	alert("function is not a sro type!");
		  	break;
		}
		return type;
	},
	
	doIntegerXML: function(data) {
		var xml = "<int>" + data + "</int>";
		return xml;
	},
	
	doDoubleXML: function(data) {
		var xml = "<double>" + data + "</double>";
		return xml;
	},

	doBooleanXML: function(data){
		var value = (data==true)?1:0;
		var xml = "<boolean>" + value + "</boolean>";
		return xml;
	},
	
	doStringXML: function(data){
		var xml = data;
		xml = xml.replace(/&/g,"&amp;");
		xml = xml.replace(/</g,"&lt;");
		xml = xml.replace(/>/g,"&gt;");
		xml = "<string>" + xml + "</string>";
		return xml;
	},
	
	doDateXML: function(data){
		var xml = "<date>";
		xml += data.getTime();
		xml += "</date>";
		return xml;
	},
	
	doArrayXML : function(data){
		var xml = "<list>\n";
		xml += "<size>";
		var size = data.length;
		xml += size;
		xml += "</size>\n";
		for (var i = 0; i < size; i++){
			xml += this.getParamXML(this.dataTypeOf(data[i]),data[i]);
		}
		xml += "</list>";
		return xml;
	},
	
	doMapXML : function(data){
		var xml = "<map>\n";
		for (var i in data){
		//alert(i);
		//alert(typeof(data[i]));
			if (typeof(data[i]) == "function") {
				continue; /* the function shouldn't transfered. */
			}
			//if (i === null) {
			//	continue;
			//}
			xml += this.getParamXML(this.dataTypeOf(i),i);
			xml += this.getParamXML(this.dataTypeOf(data[i]),data[i]);
		}
		xml += "</map>";
		return xml;
	},
	
	doNullXml : function() {
		return "<null></null>";
	},
	
	getParamXML: function(type,data){
		var xml;
		switch (type){
			case "string": xml = this.doStringXML(data); break;
			case "list": xml = this.doArrayXML(data); break;
			case "map": xml = this.doMapXML(data); break;
			case "int": xml = this.doIntegerXML(data); break;
			case "date": xml = this.doDateXML(data); break;
			case "double": xml = this.doDoubleXML(data); break;
			case "boolean": xml = this.doBooleanXML(data); break;
			case "null": xml = this.doNullXml(); break;
			default: 
				alert("type:[" + type + "] " + "has no serializer!");
		}
		xml +=  '\n';
		return xml;
	}
}


Sro.Reply = Class.create();
Sro.Reply.prototype = {
	initialize: function(sourceXML) {
		this._source = sourceXML;
		this._isFault = false;
		this._type = "null";
		this._objects = [];
		this._objectNodes = [];
		
		var xmldoc = XmlDocument.create();
		xmldoc.async=false;
		xmldoc.loadXML(sourceXML);
		var root = xmldoc.documentElement;
		this.dataNode = root.firstChild;
		this._type = this._getType(this.dataNode);
	},
	
	getType: function() { return this._type; },
	
	getResult : function() { return this.deserialize(this.dataNode); },
	
	isFault : function() { return (this._type == "fault"); },
	
	isNull: function() { return (this._type == "null"); },
	
	getSource : function() { return this._source; },
	
	deserialize: function(dataNode) {
		var ret;
		type = this._getType(dataNode);
		switch (type) {
			case "boolean": ret = this.doBoolean(dataNode); break;
			case "date": ret = this.doDate(dataNode); break;
			case "double": ret = this.doDouble(dataNode); break;
			case "int": 
			case "long": 
				ret = this.doInt(dataNode);
				break;
			case "list": ret = this.doList(dataNode); break;
			case "map": ret = this.doMap(dataNode); break;
			case "null": ret = this.doNull(dataNode); break;
			case "ref": ret = this.doRef(dataNode); break;
			case "string": ret = this.doString(dataNode);break;
			case "xml": ret = this.doXML(dataNode); break;
			case "fault": ret = this.doFault(dataNode); break;
			default: ;
		}

		return ret;
	},
	
	_getType : function(dataNode) {
		return dataNode.tagName.toLowerCase();
	},
	
	getNodeText :function(dataNode) {
		if (dataNode.childNodes.length == 0) {
			return null;
		} else 
			return dataNode.firstChild.nodeValue;
	},

	doBoolean : function (dataNode) {
		var value = this.getNodeText(dataNode);
		return (value == "1");
	},
	
	doDate : function (dataNode) {
		var value = this.getNodeText(dataNode);
		var d = new Date(parseInt(value));
		return d;
	},
	
	doDouble : function (dataNode) {
		var value = this.getNodeText(dataNode);
		return parseFloat(value);
	},
	
	doInt: function (dataNode) {
		var value = this.getNodeText(dataNode);
		return parseInt(value);
	},
	
	doList: function (dataNode) {
		var arr = new Array();
		this._objects[this._objects.length] = arr;
		var children = dataNode.childNodes;
		//TODO check the <size></size>
		for (var i = 1; i < children.length ; i++) {
			arr.push(this.deserialize(children[i]));
		}
		return arr;
	},

	doMap: function (dataNode) {
	
		var obj = new Object();
		this._objects[this._objects.length] = obj;

		var attrs = dataNode.childNodes;
		for (var i = 0; i < attrs.length; i+=2) {
			if (attrs[i+1].hasChildNodes() ) {
				obj[this.getNodeText(attrs[i])] = this.deserialize(attrs[i+1]);
			} else {
				obj[this.getNodeText(attrs[i])] = attrs[i+1].text;
			}
		}
		
		return obj;
	},
	
	doNull: function (dataNode) { return null;	},
	
	doRef: function (dataNode) {
		var value = this.getNodeText(dataNode);
		var idx = parseInt(value);
		
		return this._objects[idx];
	},
	
	doString: function (dataNode) {
		var value = this.getNodeText(dataNode);
		if (value == null) {
			return "";
		}
		return (value);
	},
	
	doXML : function (dataNode) {
		var value = this.getNodeText(dataNode);
		return unescape(value);
	},
	
	doFault : function (dataNode) {
		var code = this.getNodeText(dataNode.childNodes[1]);
		var msg = this.getNodeText(dataNode.childNodes[3]);
		var detail = this.deserialize(dataNode.childNodes[5]);
		return new Sro.Fault(code, msg, detail);
	}
}
// ------------------
// xml
// ------------------
function XmlDocument() {}
XmlDocument.create = function () {
	try {
		if (document.implementation && document.implementation.createDocument) {
			var doc = document.implementation.createDocument("", "", null);
			if (doc.readyState == null) {
				doc.readyState = 1;
				doc.addEventListener("load", function () {
					doc.readyState = 4;
					if (typeof doc.onreadystatechange == "function")
						doc.onreadystatechange();
				}, false);
			}
			
			return doc;
		}
		if (window.ActiveXObject)
			return new ActiveXObject(getDomDocumentPrefix() + ".DomDocument");
	}
	catch (ex) {}
	throw new Error("Your browser does not support XmlDocument objects");
};

if (window.DOMParser &&
	window.XMLSerializer &&
	window.Node && Node.prototype && Node.prototype.__defineGetter__) {

	Document.prototype.loadXML = function (s) {
		
		var doc2 = (new DOMParser()).parseFromString(s, "text/xml");
		
		while (this.hasChildNodes())
			this.removeChild(this.lastChild);
		for (var i = 0; i < doc2.childNodes.length; i++) {
			this.appendChild(this.importNode(doc2.childNodes[i], true));
		}
	};
	
	Document.prototype.__defineGetter__("xml", function () {
		return (new XMLSerializer()).serializeToString(this);
	});
}
function getDomDocumentPrefix() {
	if (getDomDocumentPrefix.prefix)
		return getDomDocumentPrefix.prefix;
	
	var prefixes = ["MSXML2", "Microsoft", "MSXML", "MSXML3"];
	var o;
	for (var i = 0; i < prefixes.length; i++) {
		try {
			// try to create the objects
			o = new ActiveXObject(prefixes[i] + ".DomDocument");
			return getDomDocumentPrefix.prefix = prefixes[i];
		}
		catch (ex) {};
	}
	
	throw new Error("Could not find an installed XML parser");
}

function dateToISO8609(date){
	var year = new String(date.getYear());
	var month = leadingZero(new String(date.getMonth()+1));
	var day = leadingZero(new String(date.getDate()));
	var time = leadingZero(new String(date.getHours())) + leadingZero(new String(date.getMinutes())) + leadingZero(new String(date.getSeconds()));

	var converted = year+month+day+"T"+time+"Z";
	return converted;
} 

function leadingZero(n){
	if (n.length==1) n = "0" + n;
	return n;
}
